// redisUtil.js - Redis连接工具类

// 使用ioredis替代node-redis，兼容性更好且API更稳定
const Redis = require('ioredis');
const redisConfig = require('../config/redis');

/**
 * Redis客户端实例
 */
let client = null;

/**
 * 观看次数更新队列和定时器配置
 */
const VIEW_COUNT_UPDATE_CONFIG = {
  batchSize: 50,        // 每批更新的视频数量
  interval: 600000,      // 更新间隔（毫秒） - 10分钟
  maxQueueSize: 1000    // 最大队列大小
};

/**
 * 待更新的视频ID集合
 */
let pendingViewUpdates = new Set();

/**
 * 批量更新定时器
 */
let viewUpdateTimer = null;

// 简单的布隆过滤器实现（用于防止缓存穿透）
class SimpleBloomFilter {
  constructor(size = 1000000) {
    this.size = size;
    this.hashCount = 3; // 使用3个哈希函数
  }
  
  // 生成哈希值
  _hash(str, seed) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = ((hash << 5) + hash) + str.charCodeAt(i);
      hash = hash & hash; // 转换为32位整数
      hash = Math.abs(hash);
    }
    return (hash + seed) % this.size;
  }
  
  // 获取多个哈希位置
  getHashPositions(key) {
    const positions = [];
    for (let i = 0; i < this.hashCount; i++) {
      positions.push(this._hash(key, i));
    }
    return positions;
  }
}

// 初始化布隆过滤器
const bloomFilter = new SimpleBloomFilter();

/**
 * 初始化Redis连接
 * @returns {Promise<void>}
 */
async function initRedis() {
  try {
    // 使用ioredis创建Redis客户端
    client = new Redis({
      host: redisConfig.host,
      port: redisConfig.port,
      db: redisConfig.db,
      password: redisConfig.password || undefined,
      retryStrategy: function(times) {
        return Math.min(times * 50, 2000); // 重试策略：最小50ms，最大2000ms
      },
      maxRetriesPerRequest: null // 无限重试
    });

    // 连接事件处理
    client.on('connect', () => {
      console.log('Redis连接成功');
    });

    // 错误事件处理
    client.on('error', (err) => {
      console.error('Redis连接错误:', err.message);
    });

    // 重新连接事件处理
    client.on('reconnecting', (params) => {
      console.log(`Redis重新连接中，延迟: ${params.delay}ms，尝试次数: ${params.attempt}`);
    });

    // 连接中断事件处理
    client.on('close', () => {
      console.log('Redis连接已中断');
    });

    console.log('Redis客户端就绪');
    
    // 启动观看次数更新定时器
    startViewCountUpdateTimer();
    
  } catch (error) {
    console.error('Redis初始化失败:', error.message);
    throw error;
  }
}

/**
 * 获取Redis客户端实例
 * @returns {RedisClient}
 */
function getClient() {
  return client;
}

/**
 * 生成随机过期时间
 * @param {number} baseExpire - 基础过期时间（秒）
 * @param {number} variance - 最大偏差时间（秒）
 * @returns {number} 随机过期时间
 */
function getRandomExpire(baseExpire, variance = 300) {
  const randomOffset = Math.floor(Math.random() * (variance * 2)) - variance;
  return Math.max(60, baseExpire + randomOffset); // 确保至少60秒
}

/**
 * 设置缓存
 * @param {string} key - 缓存键
 * @param {*} value - 缓存值
 * @param {number} expire - 过期时间（秒）
 * @param {boolean} useRandomExpire - 是否使用随机过期时间
 * @returns {Promise<boolean>}
 */
async function setCache(key, value, expire = null, useRandomExpire = true) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过缓存设置');
      return false;
    }
    
    // 序列化值
    const serializedValue = JSON.stringify(value);
    
    // 设置缓存 - ioredis API格式
    if (expire) {
      const finalExpire = useRandomExpire ? getRandomExpire(expire) : expire;
      await client.set(key, serializedValue, 'EX', finalExpire);
    } else {
      await client.set(key, serializedValue);
    }
    
    return true;
  } catch (error) {
    console.error('设置缓存失败:', error.message);
    return false;
  }
}

/**
 * 获取缓存
 * @param {string} key - 缓存键
 * @param {boolean} useBloomFilterCheck - 是否使用布隆过滤器检查
 * @returns {Promise<*>}
 */
async function getCache(key, useBloomFilterCheck = false) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过缓存获取');
      return null;
    }
    
    // 使用布隆过滤器进行前置检查，防止缓存穿透
    if (useBloomFilterCheck) {
      const exists = await checkBloomFilter(key);
      if (!exists) {
        return null;
      }
    }
    
    // 获取缓存
    const value = await client.get(key);
    
    // 反序列化值
    return value ? JSON.parse(value) : null;
  } catch (error) {
    console.error('获取缓存失败:', error.message);
    return null;
  }
}

/**
 * 删除缓存
 * @param {string} key - 缓存键
 * @returns {Promise<boolean>}
 */
async function deleteCache(key) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过缓存删除');
      return false;
    }
    
    await client.del(key);
    return true;
  } catch (error) {
    console.error('删除缓存失败:', error.message);
    return false;
  }
}

/**
 * 递增计数器
 * @param {string} key - 计数器键
 * @param {number} increment - 递增量
 * @returns {Promise<number|null>}
 */
async function incrementCounter(key, increment = 1) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过计数器递增');
      return null;
    }
    
    // 使用ioredis API格式（小写）
    return await client.incrby(key, increment);
  } catch (error) {
    console.error('递增计数器失败:', error.message);
    return null;
  }
}

/**
 * 设置过期时间
 * @param {string} key - 键
 * @param {number} seconds - 过期时间（秒）
 * @returns {Promise<boolean>}
 */
async function expire(key, seconds) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过过期时间设置');
      return false;
    }
    
    await client.expire(key, seconds);
    return true;
  } catch (error) {
    console.error('设置过期时间失败:', error.message);
    return false;
  }
}

/**
 * 检查键是否存在
 * @param {string} key - 键
 * @returns {Promise<boolean>}
 */
async function exists(key) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，默认返回false');
      return false;
    }
    
    const result = await client.exists(key);
    return result > 0;
  } catch (error) {
    console.error('检查键存在性失败:', error.message);
    return false;
  }
}

/**
 * 根据模式删除缓存
 * @param {string} pattern - 键模式
 * @returns {Promise<number>}
 */
async function deleteCachePattern(pattern) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过缓存删除');
      return 0;
    }
    
    // 查找匹配的键
    const keys = await client.keys(pattern);
    
    if (keys.length === 0) {
      return 0;
    }
    
    // 批量删除
    await client.del(keys);
    return keys.length;
  } catch (error) {
    console.error('删除缓存模式失败:', error.message);
    return 0;
  }
}

/**
 * 添加到布隆过滤器
 * @param {string} key - 键
 * @returns {Promise<boolean>}
 */
async function addToBloomFilter(key) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过布隆过滤器添加');
      return false;
    }
    
    const positions = bloomFilter.getHashPositions(key);
    const filterKey = 'bloom_filter:keys';
    
    // 使用pipeline批量操作
    const pipeline = client.multi();
    for (const position of positions) {
      pipeline.setBit(filterKey, position, 1);
    }
    await pipeline.exec();
    
    // 设置布隆过滤器键的过期时间为7天
    await client.expire(filterKey, 60 * 60 * 24 * 7);
    
    return true;
  } catch (error) {
    console.error('添加到布隆过滤器失败:', error.message);
    return false;
  }
}

/**
 * 检查布隆过滤器
 * @param {string} key - 键
 * @returns {Promise<boolean>}
 */
async function checkBloomFilter(key) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，默认返回true');
      return true;
    }
    
    const positions = bloomFilter.getHashPositions(key);
    const filterKey = 'bloom_filter:keys';
    
    // 使用pipeline批量操作
    const pipeline = client.multi();
    for (const position of positions) {
      pipeline.getBit(filterKey, position);
    }
    const results = await pipeline.exec();
    
    // 检查所有位是否都为1
    for (const [_, result] of results) {
      if (result === 0) {
        return false;
      }
    }
    
    return true;
  } catch (error) {
    console.error('检查布隆过滤器失败:', error.message);
    return true; // 失败时默认返回true，不影响主流程
  }
}

/**
 * 初始化视频浏览量计数器
 * @param {Object} Video - Video模型
 * @returns {Promise<void>}
 */
async function initViewCountCounters(Video) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过浏览量计数器初始化');
      return;
    }
    
    console.log('开始初始化视频浏览量计数器...');
    
    // 从数据库查询所有视频的ID和观看量
    const { sequelize } = require('../config/db');
    const query = 'SELECT id, view_count FROM videos WHERE status = 1';
    const videos = await sequelize.query(query, {
      type: sequelize.QueryTypes.SELECT
    });
    
    if (videos && videos.length > 0) {
      console.log(`准备初始化${videos.length}个视频的浏览量计数器`);
      const pipeline = client.pipeline();
      
      for (const video of videos) {
        const counterKey = `video:view:${video.id}`;
        // 检查计数器是否存在
        const exists = await client.exists(counterKey);
        
        if (exists === 0) {
          // 如果计数器不存在，初始化它
          console.log(`[缓存调试] 初始化浏览量计数器，ID: ${video.id}，值: ${video.view_count || 0}`);
          await client.set(counterKey, video.view_count || 0);
        } else {
          // 如果计数器已存在，获取现有值
          const currentValue = await client.get(counterKey);
          const currentCount = parseInt(currentValue) || 0;
          const dbCount = video.view_count || 0;
          
          // 确保Redis中的值至少等于数据库中的值
          if (currentCount < dbCount) {
            console.log(`[缓存调试] 更新浏览量计数器，ID: ${video.id}，从${currentCount}更新到${dbCount}`);
            await client.set(counterKey, dbCount);
          }
        }
        
        // 设置过期时间为24小时
        await client.expire(counterKey, 86400);
      }
      
      await pipeline.exec();
      console.log('视频浏览量计数器初始化完成');
    }
  } catch (error) {
    console.error('初始化视频浏览量计数器失败:', error.message);
  }
}

/**
 * 缓存预热
 * @param {Object} Video - Video模型
 * @returns {Promise<void>}
 */
async function warmupCache(Video) {
  try {
    console.log('开始缓存预热...');
    
    // 初始化热门视频zset
    await initHotVideosZset(Video);
    
    // 初始化推荐视频zset
    await initRecommendedVideosZset(Video);
    
    // 初始化视频浏览量计数器
    await initViewCountCounters(Video);
    
    // 预热幻灯片视频缓存
    const slideVideos = await Video.getSlideVideos();
    if (slideVideos && slideVideos.length > 0) {
      await setCache('videos:slide', slideVideos, 3600); // 1小时过期
    }
    
    console.log('缓存预热完成');
  } catch (error) {
    console.error('缓存预热失败:', error.message);
  }
}

/**
 * 初始化热门视频有序集合
 * @param {Object} Video - Video模型
 * @param {number} maxCount - 最大数量
 * @returns {Promise<void>}
 */
async function initHotVideosZset(Video, maxCount = 100) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过热门视频zset初始化');
      return;
    }
    
    // 获取热门视频数据，传递skipInitZset=true避免循环调用
    const videos = await Video.getHotVideos(1, maxCount, true);
    const zsetKey = 'videos:hot:zset';
    
    // 清空旧的zset
    await client.del(zsetKey);
    
    // 添加到zset
        if (videos && videos.length > 0) {
          const pipeline = client.pipeline();
          for (const video of videos) {
            // 使用观看量作为score，适配ioredis API
            pipeline.zadd(zsetKey, video.view_count || 0, JSON.stringify(video));
          }
          await pipeline.exec();
          console.log(`热门视频zset初始化完成，共${videos.length}个视频`);
        }
  } catch (error) {
    console.error('初始化热门视频zset失败:', error.message);
  }
}

/**
 * 初始化推荐视频有序集合
 * @param {Object} Video - Video模型
 * @param {number} maxCount - 最大数量
 * @returns {Promise<void>}
 */
async function initRecommendedVideosZset(Video, maxCount = 100) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，跳过推荐视频zset初始化');
      return;
    }
    
    // 获取推荐视频数据，传递skipInitZset=true避免循环调用
    const videos = await Video.getRecommendedVideos(1, maxCount, true);
    const zsetKey = 'videos:recommended:zset';
    
    // 清空旧的zset
    await client.del(zsetKey);
    
    // 添加到zset
    if (videos && videos.length > 0) {
      const pipeline = client.pipeline();
      for (const video of videos) {
        // 使用收藏量作为score，适配ioredis API
        pipeline.zadd(zsetKey, video.collect_count || 0, JSON.stringify(video));
      }
      await pipeline.exec();
      
      console.log(`推荐视频zset初始化完成，共${videos.length}个视频`);
    }
  } catch (error) {
    console.error('初始化推荐视频zset失败:', error.message);
  }
}

/**
 * 限流检查
 * @param {string} key - 限流键
 * @param {number} limit - 限制次数
 * @param {number} expire - 过期时间（秒）
 * @returns {Promise<boolean>}
 */
async function isAllowed(key, limit = 100, expire = 60) {
  try {
    if (!client) {
      console.warn('Redis客户端未初始化，默认允许请求');
      return true;
    }
    
    const current = await client.incr(key);
    
    if (current === 1) {
      // 第一次访问，设置过期时间
      await client.expire(key, expire);
    }
    
    return current <= limit;
  } catch (error) {
    console.error('限流检查失败:', error.message);
    return true; // 失败时默认允许请求
  }
}

/**
 * 排队观看次数更新
 * @param {number} videoId - 视频ID
 */
function queueViewCountUpdate(videoId) {
  try {
    // 检查队列大小
    if (pendingViewUpdates.size >= VIEW_COUNT_UPDATE_CONFIG.maxQueueSize) {
      console.warn('观看次数更新队列已满，丢弃更新请求');
      return;
    }
    
    // 添加到队列
    pendingViewUpdates.add(videoId);
  } catch (error) {
    console.error('队列观看次数更新失败:', error.message);
  }
}

/**
 * 批量更新观看次数
 * @param {Function} updateCallback - 更新回调函数
 * @returns {Promise<void>}
 */
async function batchUpdateViewCounts(updateCallback = null) {
  try {
    if (pendingViewUpdates.size === 0) {
      return;
    }
    
    console.log(`开始批量更新观看次数，待更新视频数: ${pendingViewUpdates.size}`);
    
    // 获取待更新的视频ID数组
    const videoIds = Array.from(pendingViewUpdates);
    // 清空队列
    pendingViewUpdates.clear();
    
    // 批量更新
    if (updateCallback) {
      await updateCallback(videoIds);
    } else {
      // 默认更新逻辑：直接更新数据库并清除相关缓存
      const { sequelize } = require('../config/db');
      const Video = require('../models/Video');
      
      for (const videoId of videoIds) {
        try {
          // 1. 获取Redis中的计数器值
          const counterKey = `video:view:${videoId}`;
          const counterValue = await client.get(counterKey);
          
          if (counterValue && parseInt(counterValue) > 0) {
            console.log(`[缓存调试] 批量更新视频${videoId}的观看次数: ${counterValue}`);
            
            // 2. 更新数据库中的观看次数
            const updateQuery = `UPDATE videos SET view_count = view_count + :increment WHERE id = :id`;
            const [affectedRows] = await sequelize.query(updateQuery, {
              replacements: { id: videoId, increment: parseInt(counterValue) },
              type: sequelize.QueryTypes.UPDATE
            });
            
            if (affectedRows > 0) {
              // 3. 清除视频详情缓存，确保下次请求获取最新数据
              await deleteCache(`video:detail:${videoId}`);
              
              // 4. 重置Redis计数器
              await client.set(counterKey, 0);
              console.log(`[缓存调试] 视频${videoId}观看次数更新成功，已清除缓存`);
            }
          }
        } catch (videoError) {
          console.error(`更新视频${videoId}观看次数失败:`, videoError.message);
          // 继续处理下一个视频
        }
      }
    }
    
    console.log('观看次数批量更新完成');
  } catch (error) {
    console.error('批量更新观看次数失败:', error.message);
  }
}

/**
 * 开始观看次数更新定时器
 */
function startViewCountUpdateTimer() {
  try {
    // 先停止旧的定时器
    stopViewCountUpdateTimer();
    
    // 启动新的定时器
    viewUpdateTimer = setInterval(() => {
      batchUpdateViewCounts();
    }, VIEW_COUNT_UPDATE_CONFIG.interval);
    
    console.log('观看次数更新定时器已启动');
  } catch (error) {
    console.error('启动观看次数更新定时器失败:', error.message);
  }
}

/**
 * 停止观看次数更新定时器
 */
function stopViewCountUpdateTimer() {
  try {
    if (viewUpdateTimer) {
      clearInterval(viewUpdateTimer);
      viewUpdateTimer = null;
      console.log('观看次数更新定时器已停止');
    }
  } catch (error) {
    console.error('停止观看次数更新定时器失败:', error.message);
  }
}

/**
 * 关闭Redis客户端连接
 * @returns {Promise<void>}
 */
async function closeRedisClient() {
  try {
    if (client && client.status !== 'end') {
      await client.quit();
      console.log('Redis连接已关闭');
    }
  } catch (error) {
    console.error('关闭Redis连接失败:', error.message);
  }
}

module.exports = {
  initRedis,
  getClient,
  setCache,
  getCache,
  deleteCache,
  incrementCounter,
  expire,
  exists,
  deleteCachePattern,
  addToBloomFilter,
  checkBloomFilter,
  warmupCache,
  initHotVideosZset,
  initRecommendedVideosZset,
  isAllowed,
  queueViewCountUpdate,
  startViewCountUpdateTimer,
  stopViewCountUpdateTimer,
  batchUpdateViewCounts,
  closeRedisClient
};